#import "template.typ": book
#show: rest => book("Python 小册子", rest)

#show regex("JS\W+"): "JavaScript" // 把 JS 替换为 JavaScript

= 安装

在 macOS 系统使用 Homebrew 安装 Python 3

```sh
$ brew install python3
```

为了检查安装是否成功，可以执行 `python3 -V` 检查版本号。

```sh
$ python3 -V
Python 3.12.5
```

== Hello world

编写文件 `hello.py`，内容如下：

```py
print("Hello, world!")
```

执行 `pyhton3 hello.py`，终端窗口打印输出 "Hello, world!"。

使用 `#` 为代码添加注释。

```py
# 我是注释
print("Hello, world!")
```

== Python 之禅

在 Python3 运行环境中执行 `import this` 命令，可以打印 Python 之禅（The Zen of Python）。

Python 之禅是一组指导原则，使用简单句子展示 Python 的哲学和设计思想。

#pagebreak()

#quote[
#set text(11pt)

*The Zen of Python, by Tim Peters* \
\
Beautiful is better than ugly. \
Explicit is better than implicit. \
Simple is better than complex. \
Complex is better than complicated. \
Flat is better than nested. \
Sparse is better than dense. \
Readability counts. \
Special cases aren't special enough to break the rules. \
Although practicality beats purity. \
Errors should never pass silently. \
Unless explicitly silenced. \
In the face of ambiguity, refuse the temptation to guess. \
There should be one-- and preferably only one --obvious way to do it. \
Although that way may not be obvious at first unless you're Dutch. \
Now is better than never. \
Although never is often better than *right* now. \
If the implementation is hard to explain, it's a bad idea. \
If the implementation is easy to explain, it may be a good idea. \
Namespaces are one honking great idea -- let's do more of those! \
]

#pagebreak()

= 内置类型

Python 的基本类型包括字符串、数值、布尔值等。

== 字符串

字符串使用单引号或双引号包裹。

```py
name = "Tony Stark"
print(name)
```

字符串可以使用 `+` 拼接。

```py
"Hello, " + name + "!"
# 'Hello, Tony Stark!'
```

Python 字符串支持乘法运算符 `*`。

```py
"重要的事情说三遍！" * 3
# '重要的事情说三遍！重要的事情说三遍！重要的事情说三遍！'
```

字符串中可以包含转义字符，这是一种以反斜线 `\` 开头的特殊字符，比如换行符 `\n`。

```py
"第一行\n第二行"
# 第一行
# 第二行
```

字符串的常用方法如下：

```py
# 获取字符串长度
len("Tony Stark") # 10

# 删除首尾空格
"   hello world    ".strip() # 'hello world'

# 返回小写形式
"Tony Stark".lower() # 'tony stark'

# 返回大写形式
"Tony Stark".upper() # 'TONY STARK'

# 把字符串拆分为列表
'hello\nworld\nfoobar'.split('\n')
# ['hello', 'world', 'foobar']

# 把列表串联为一个字符串
'+'.join(['a', 'b', 'c']) # 'a+b+c'

# 查找字符串出现的位置，找不到返回 -1
'hello world'.find('wo') # 6
'hello world'.find('z') # -1

# 替换所有字符串
'aaabbbccc'.replace('b', 'z') # 'aaazzzccc'
# 替换前两个字符串
'aaabbbccc'.replace('b', 'z', 2) # 'aaazzbccc'
```

使用成员运算符 `in` 可以检测字符串是否存在。

```py
'wo' in 'hello world' # True
```

=== 格式化字符串

字符串的 `format()` 方法可以格式化输出数字和字符串等变量。

```py
# 格式化输出
'I have {} {}'.format(2, 'apples')
# 'I have 2 apples'

# 格式化输出时调整数字格式
'{}的价格是{:0,.2f}元'.format('苹果', 4.2)
# '苹果的价格是4.20元'
```

字符串的 `format_map()` 方法，可以格式化输出字典类型的值。

```py
peter = {
    'name': 'Peter Parker',
    'hero': 'Spiderman'
}
'{name} is {hero}'.format_map(peter)
# 'Peter Parker is Spiderman'
```

为了让字符串插值更简单，Python 3.6.0 @python360 引入了 `f-string`，即以 `f` 开头的字符串。

```py
name = 'Peter'
hero = 'Spiderman'
f'{name} is {hero}'
# 'Peter is Spiderman'

age = 17
f'今年 {age} 岁，明年 {age+1} 岁'
# '今年 17 岁，明年 18 岁'

# 支持更多高级选项
import datetime
today = datetime.date(2024, 8, 22)
f'today is {today:%A, %B %d, %Y}'
# 'today is Thursday, August 22, 2024'
```

=== 获取用户输入

使用 `input()` 函数获取用户输入，返回值是字符串类型。

```py
name = input("你叫什么名字？")
print("你好，" + name)
```

`input` 和 `print` 函数一样，都是 Python 的内置函数。

== 数值

数值类型包括整数、浮点数和复数。

```py
num = 3 # 整数
pi = 3.14 # 浮点数
a = 1 + 2j # 复数
```

数字可以进行加减乘除，取余，指数等运算。

```py
2 + 3 # 5
3 - 2 # 1
2 * 3 # 6

# 浮点数除法
10 / 3 # 3.3333333333333335
# 整数除法
10 // 3 # 3

# 取余操作
10 % 3 # 1

# 内置函数 divmod 可以同时得到商和余数
divmod(9, 5)
# (1, 4)

# 指数运算
2 ** 3 # 8
```

在 JS 中，任何数除以 `0`，结果是无穷大 `Infinity`。在 Python 中，除以 0 会报错。

```py
>>> 5 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
```

=== 进制

数值默认使用十进制。通过不同的前缀使用其他进制。

```py
10 # 十进制

0b10 # 二进制
# 2

0o10 # 八进制
# 8

0x10 # 十六进制
# 16
```

=== 类型转换

数字类型和字符串不能通过 `+` 直接拼接，因为它俩不是同一种类型。

```py
num = 2
"I have " + num + " apples"
```

```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
```

此时需要使用 `str()` 方法，将数字转换为字符串，然后再拼接：

```py
num = 2
"I have " + str(num) + " apples"
# 'I have 2 apples'
```

要想把字符串转换为数字，可以使用 `int()` 或 `float()` 方法。

```py
int("6") * 2 # 12
float("3.14") * 4 # 12.56
```

注意，`int` 函数对于入参格式要求十分严格，如果不是整数，就会报错。

```py
>>> int('3.14')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '3.14'
```

=== 格式化输出

使用 `format()` 全局函数，可以对数值进行格式化输出，它的返回值是字符串类型。

```py
# 保留小数点后两位
format(1234.567, '0.2f') # '1234.57'

# 使用千分位分隔符
format(12345.6789, ',') # '12,345.6789'

# 保留小数点后两位，同时使用千分位分隔符
format(1234567, '0,.2f') # '1,234,567.00'
```

== 布尔值

Python 的布尔值用 `True` 和 `False` 表示。

```py
ok = True
hungry = False
```

比较运算符的结果是布尔值。

```py
3 > 2 # true
```

== 列表

列表相当于 JavaScript 中的数组，可以容纳多个值。

```py
nums = [9, 5, 2, 7]
```

列表可以通过 `+` 相加：

```py
[9, 5] + [2, 7] # [9, 5, 2, 7]
```

Python 内置许多对于数组的基本操作。

```py
# 获取数组元素个数
len(nums) # 4

# 计算最大值和最小值
max(nums) # 9
min(nums) # 2

# 排序（从小到大）
sorted(nums) # [2, 5, 7, 9]
# 逆向排序（从大到小）
sorted(nums, reverse = True) # [9, 7, 5, 2]
```

列表元素类型可以不同，且内容可变。

```py
nums = [9, 5, 2, 7]
nums[1] = 'x'
nums # [9, 'x', 2, 7]
```

通过 `append(item)` 方法向列表末尾增加一个元素。

```py
nums = [9, 5, 2, 7]
nums.append(6)
nums # [9, 5, 2, 7, 6]
```

`extend(list)` 方法可以扩展多个元素

```py
a = [1, 2, 3]
b = [4, 5, 6]
a.extend(b)
a # [1, 2, 3, 4, 5, 6]
```

`insert(index, item)` 方法在指定位置插入一个值。

```py
a = [1, 2, 3]
a.insert(1, 'two')
a # [1, 'two', 2, 3]
```

使用 `del` 关键词根据位置删除元素。

```py
nums = [9, 5, 2, 7]
del nums[0]
nums # [5, 2, 7]
```

`remove(value)` 方法根据值删除第一个元素。

```py
a = [1, 2, 1]
a.remove(1)
a # [2, 1]
```

`pop(index)` 根据位置移除元素并将其返回。`index` 默认为 `-1`，即移除最后一个元素。

```py
a = [1, 2, 3, 4]
a.pop() # 4
a # [1, 2, 3]
a.pop(1) # 2
a # [1, 3]
```

`index(val)` 查找某个值在列表中的位置。找不到就报错。

```py
a = ['a', 'b', 'c', 'd']
a.index('c') # 2

a.index('z')
# ValueError: 'z' is not in list
```

使用 `in` 运算符判断某个元素是否存在。

```py
a = ['a', 'b', 'c', 'd']

'a' in a # True
'z' in a # False
```

`count(val)` 方法统计某个元素出现的次数。

```py
a = [1, 2, 1]
a.count(1) # 2
a.count(10) # 0
```

`copy()` 方法浅复制列表内容。

```py
a = [1, 2, 3]
b = a.copy() # b 是 a 的浅复制
b[1] = 'two'
b # [1, 'two', 3]
a # [1, 2, 3]
```

=== 切片

使用切片截取数组特定区间的元素。切片的语法是 `[start:end]`，不包含 `end` 位置的元素。

如果省略 `start`，则从 0 开始。如果省略 `end`，则到最后一个元素为止。

```py
nums = [9, 5, 2, 7]
s = nums[1: 3]
s # [5, 2]

nums[:3] # [9, 5, 2]
nums[1:] # [5, 2, 7]
nums[:] # [9, 5, 2, 7]
```

=== 二维列表

列表的元素可以是任意类型，包括列表。二维列表指的就是包含列表的列表。

比如，把一个班级所有学生的分数储存在二维列表中。

```py
scores = [
    # [姓名 数学 语文 英语]
    ['张三', 96, 85, 70],
    ['李四', 82, 75, 67],
    ['王五', 56, 100, 90],
]
```

若要对二维列表排序，需要指定排序规则，即针对哪个字段排序。`sorted()` 函数的 `key` 参数用于接收自定义排序规则。

`key` 的取值有多种，最简单的是使用 `operator` 模块 @operator 的 `itemgetter` 函数。

比如，我们想按照英语成绩从高到底排序，英语成绩的索引位置是 3，代码如下：

```py
from operator import itemgetter

scores = [
    # [姓名 数学 语文 英语]
    ['张三', 96, 85, 70],
    ['李四', 82, 75, 67],
    ['王五', 56, 100, 90],
]

sorted_scores = sorted(
    scores, 
    key = itemgetter(3), # 按照英语排序
    reverse = True # 逆向排序
)

print(sorted_scores)
# [['王五', 56, 100, 90], ['张三', 96, 85, 70], ['李四', 82, 75, 67]]
```

=== 列表推导式

列表推导式（list comprehension）是一种构造列表的简洁方式。它的作用是，在现有列表的基础上，计算得到新的列表。语法如下：

```py
expression for item in iterable if condition
```

举个例子，筛选所有包含 `'a'` 的水果：

```py
fruits = ['apple', 'banana', 'cherry', 'kiwi']
result = [item for item in fruits if 'a' in item]
result # ['apple', 'banana']
```

== 元组

元组（tuple）可看作不可变的列表，可以使用圆括号包裹，也可以不用括号。

```py
color = (255, 255, 100)
fields = 'name', 'age', 'gender'
```

如果尝试修改元组元素，会报错：

```py
color[0] = 100

# TypeError: 'tuple' object does not support item assignment
```

元组也支持切片操作，结果仍然是元组类型。

```py
t = (1, 2, 3, 4)
t[1: 3] # (2, 3)
```

和列表相比，元组的优势在于：

- 占用的体积小
- 不会被意外修改
- 可以当做字典的键

比如，以经纬度为键存储城市名称。

```py
cities = {
    (139.84, 35.68): '东京',
    (126.99, 37.55): '首尔'
}
cities[(139.84, 35.68)] # '东京'
```

== 集合

集合中没有重复值。

=== 创建

创建集合有两种方法，一种是集合字面量，一种使用 `set()` 构造函数，将列表、元组、字符串等其他类型转换为集合。

```py
# 集合字面量
my_set = { 1, 2, 3, 4, 4, 2, 2, 1 }
my_set # { 1, 2, 3, 4 }

# set 构造函数
a_set = set(['hello', 'world', 'foo', 'bar', 'foo'])
a_set # {'hello', 'foo', 'bar', 'world'}

a_set = set('hello')
a_set # {'o', 'h', 'e', 'l'}

# 创建空集合
empty_set = set()
```

=== 更新

更新集合的常见操作如下：

```py
a_set = {1, 2, 3}

# 添加一个元素
a_set.add(4)
a_set # {1, 2, 3, 4}

# 添加多个值
a_set.update([5, 6])
a_set # {1, 2, 3, 4, 5, 6}

# 检测元素是否位于集合中
2 in a_set # True

# 删除某个元素
a_set.remove(6)
a_set # {1, 2, 3, 4, 5}

# 清空所有元素
a_set.clear()
a_set # set()
```

使用 `remove()` 方法需注意，如果删除的值不存在，Python 会报错。为了避免报错，可以使用 `discard()` 方法。

```py
a = {1, 2, 3}
a.remove(4) # KeyError: 4

a.discard(4) # 没有报错，悄悄忽略
a.discard(3)
a # {1, 2}
```

=== 数学运算

两个集合之间可以进行数学运算，比如交集、并集和差集等。你可以使用集合的方法，也可以使用运算符，二者是等价的。

交集 $A sect B$ 指的是两个集合重复的部分。

```py
# 定义两个集合
A = {1, 2, 3}
B = {2, 3, 4}

A.intersection(B) # {2, 3}
A & B # {2, 3}
```

并集 $A union B$ 表示两个集合元素经合并去重后组成的集合。

```py
A.union(B) # {1, 2, 3, 4}
A | B # {1, 2, 3, 4}
```

差集 $A - B$ 表示只存在于集合 $A$，但不属于集合 $B$ 的元素构成的集合。

```py
A.difference(B) # {1}
A - B # {1}
```

对称差集 $A Delta B$ 表示属于 $A$ 或 $B$，但不属于它们交集的元素构成的集合。

```py
A.symmetric_difference(B) # {1, 4}
A ^ B # {1, 4}
```

=== 关系比较

比较两个集合时，元素顺序并不重要。

双等号 `==` 比较两个集合是否包含相同的元素。

```py
a = {1, 2, 3}
b = {3, 2, 1}
a == b # True
```

`issubset` 方法判断集合是否另一个集合的子集。

```py
a = {1, 2, 3}
b = {1, 2, 3, 4}
a.issubset(b) # True
b.issubset(a) # False

# 集合是自己的子集
a.issubset(a) # True

a <= b # True
```

`issuperset` 判断集合是否另一集合的父集。

```py
a = {1, 2, 3}
b = {1, 2, 3, 4}

b.issuperset(a) # True
b.issuperset(b) # True
b >= a # True
```

`isdisjoint` 判断两个集合的交集是否为空。英文单词 `disjoint` 是（两个集合）不相交的意思。

```py
a = {1, 2}
b = {3, 4}
c = {2, 3, 4}

a.isdisjoint(b) # True
a.isdisjoint(c) # False
```

== 字典

字典类型存储键值对，相当于 JS 中的 `Object` 对象。可以使用字面量创建字典，也可以使用 `dict()` 构造函数。

```py
# 字典字面量
tony = {
    'name': 'Tony Stark',
    'age': 42,
    'hero': 'Iron man'
}

# 构造函数
tony = dict(name = 'Tony Stark', age = 42, hero = 'Iron man')
```

字典的原理是使用 `hash()` @hash 函数，把键转换为数字，然后把值存储到数字对应的内存中。读取时也是同样的流程。

=== 访问

使用方括号 `[]` 或 `get()` 方法访问字典特定字段的值。

```py
tony['name'] # 'Tony Stark'
tony.get('name') # 'Tony Stark'
```

使用 `in` 运算符可以检测某个键是否存在。

```py
tony = { 'name': 'Tony Stark' }

'name' in tony # True
'age' in tony # False
```

=== 更新

可以通过键名更新单个字段。

```py
tony = { 'name': 'Tony Stark', 'age': 42 }

tony['age'] = 43 # 更新单个字段
tony # { 'name': 'Tony Stark', 'age': 43 }

tony['nick'] = 'Iron man' # 新增字段
tony
# {'name':'Tony Stark', 'age':43, 'nick':'Iron man'}
```

也可以通过 `update()` 方法批量更新多个值。

```py
tony.update(name = 'Tony Parker', age = 18)
tony
# {'name':'Tony Parker', 'age':18, 'nick':'Iron man'}
```

`setdefault()` 方法可以为字典设定默认值。

```py
tony = {'name': 'Tony Stark'}

# 当值存在时，设置无效
tony.setdefault('name', 'Iron man')
tony # {'name': 'Tony Stark'}

# 当值不存在时，设置生效
tony.setdefault('age', 44)
tony # {'name': 'Tony Stark', 'age': 44}
```

=== 删除

要删除某一字段，可以使用 `pop()` 方法。

```py
tony.pop('nick') # 'Iron man'
tony # {'name':'Tony Parker', 'age':18}
```

使用 `popitem()` 方法删除最后一个键值对，返回值是元组类型，存有被删除的键名和值。

```py
tony.popitem() # ('age', 18)
tony # {'name':'Tony Parker'}
```

`clear()` 方法清空整个字典。

```py
tony.clear()
tony # {}
```

=== 视图

字典的视图有三种，分别是：

+ `keys()` 返回字典键组成的集合
+ `values()` 返回字典值组成的集合
+ `items()` 返回字典键值对组成的集合

```py
tony = { 'age': 42, 'nick': 'Ironman' }
tony.keys() # dict_keys(['age', 'nick'])
tony.values() # dict_values([42, 'Ironman'])
tony.items()
# dict_items([('age', 42), ('nick', 'Ironman')])
```

当字典内容变化时，视图变量随之发生变化。

```py
tony = { 'age': 42, 'nick': 'Ironman' }

ks = tony.keys()
ks # dict_keys(['age', 'nick'])

tony['height'] = 172
ks # dict_keys(['age', 'nick', 'height'])
```

=== 遍历

使用 `for` 循环可以遍历字典的所有字段。

```py
a_dict = {
    'tony': 'Iron Man',
    'peter': 'Spiderman',
    'brunce': 'Batman'
}

for key in a_dict:
    value = a_dict[key]
    print(key + ': ' + value)

# tony: Iron Man
# peter: Spiderman
# bruce: Batman
```

== 变量类型

使用 `type()` 函数获取变量类型。

```py
type("hello")
# <class 'str'>

type(1)
# <class 'int'>

type(3.14)
# <class 'float'>

type(1 + 2j)
# <class 'complex'>

type(True)
# <class 'bool'>

type([1, 2, 3, 4])
# <class 'list'>

type({ 'name': 'Tony Stark' })
# <class 'dict'>
```

#pagebreak()

= 语句

== 条件语句

使用 `if else` 语句编写条件语句。Python 使用缩进标识代码层级，根据 PEP8 @pep8 的规定，缩进使用 4 个空格。

```py
age = 16
if age > 18:
    print("adult")
else:
    print("teenager")
```

如果有更多分支，可以增加 `elif` 子句。

```py
score = 60

if score >= 90:
    print("A")
elif score >= 80:
    print("B")
elif score >= 60:
    print("C")
else:
    print("D")
```

=== 逻辑运算符

如果需要综合多个条件表达式，可以使用逻辑运算符。Python 的逻辑与或非分别用 `and`、`or` 和 `not` 表示。

`and` 表示*逻辑与*，仅当所有条件都为真时，结果才是真。其余情况下，结果一律为假。

```py
gender = "m"
age = 19

if gender == "m" and age >= 18:
    print("Man")
else:
    print("Not a man")
```

Python 的比较运算符支持*链式调用* @comparisons。比如 `x < y <= z`，相当于 `x < y and y <= z`。链式调用代码简单，而且可读性好。

`or` 表示*逻辑或*。只要参与元素的任一变量为真，则结果为真。

```py
age = 24
height = 1.3

if 6 <= age <= 14 or 1.2 <= height <= 1.5:
    print("享受半价优待")
else:
    print("不享受半价优待")
```

`not` 表示*逻辑非*。

```py
rainy = False

if not rainy:
    print("出去玩")
else:
    print("在家写作业")
```

=== 语句占位符

有时候，你还没想好具体的代码逻辑，可以使用 `pass` 关键字临时充当语句的占位符。

```py
rich = False
if rich:
    pass
else:
    print("work, work")
    quit() # 终止程序
```

== 循环语句

=== while 循环

当终止条件明确时，可以使用 while 循环。

```py
i = 1
sum = 0

while i <= 100:
    sum = sum + i
    i = i + 1

print("总和是 " + str(sum))
# 总和是 5050
```

比如，使用 while 循环实现的二分查找法。

```py
def binary_search(arr, x):
    '''查找元素的索引值，如果没找到，返回 -1'''
    low = 0
    high = len(arr) - 1
    mid = 0

    while low <= high:
        mid = (high + low) // 2

        if arr[mid] < x:
            low = mid + 1
        elif arr[mid] > x:
            high = mid - 1
        else:
            return mid
    
    # 没找到时返回 -1
    return -1

nums = [2, 5, 7, 9]
binary_search(nums, 5) # 1
binary_search(nums, 11) # -1
```

使用 `break` 关键字可以从循环中跳出。`continue` 跳过当前循环的内容。

循环可以和 `else` 子句搭配使用。如果循环正常结束，即没有被 `break` 打断，就执行 `else` 子句。否则不执行 `else` 子句。

```py
a = [1, 2, 3, 4]
i = 0

while i < len(a):
    print(a[i])
    i += 1
else:
    print('I am else')
# 1
# 2
# 3
# 4
# I am else
```

=== for 循环

for 循环经常和 `range()` 一起使用。`range()` 函数可以生成一个范围内的数字序列，可以指定起止数字和步长。

```py
for i in range(3):
    print(i)
# 0
# 1
# 2

for i in range(1, 3):
    print(i)
# 1
# 2

for i in range(1, 10, 2):
    print(i)
# 1
# 3
# 5
# 7
# 9
```

for 循环也常用于遍历列表变量。

```py
nums = [9, 5, 2, 7]
for i in nums:
    print(i)
# 9
# 5
# 2
# 7
```

#pagebreak()

= 函数

函数是一段被封装的代码，可以被反复调用。

Python 内置许多实用函数。比如，使用 `round()` 函数计算数字的近似值。

```py
round(3.14) # 3
```

如果想查看某个函数的使用说明，可以执行 `help()` 函数。

```py
help(round)
```

```
Help on built-in function round in module builtins:

round(number, ndigits=None)
    Round a number to a given precision in decimal 
    digits.

    The return value is an integer if ndigits is 
    omitted or None. Otherwise the return value 
    has the same type as the number.  ndigits may 
    be negative.
```

== 定义函数

使用 `def` 关键字声明自定义函数。

在函数内部使用三引号定义*文档字符串*，可以被 `help()` 方法看到。

```py
def hello(msg):
    """a simple hello function"""
    print("Hello, " + msg + "!")

hello("Tony") # Hello, Tony!
```

```py
# 查看文档字符串
help(hello)

# hello(msg)
#     a simple hello function
```

使用 `return` 关键字返回结果。

```py
def add(a, b):
    """A simple add function"""
    return a + b

sum = add(3, 4) # 7
```

== 设置参数默认值

使用 `=` 为参数设定默认值。

```py
def add(a, b = 2):
    return a + b

add(1, 4) # 5
add(1) # 3
```

注意，拥有默认值的参数后面，必须全部拥有默认值。

```py
# 这是一个错误的例子
def add(a = 2, b):
    return a + b

# SyntaxError: parameter without a default follows parameter with a default
```

== 关键字传参

使用关键字传参，可以让代码可读性更好，并且无需严格遵循函数定义时的参数顺序。

```py
def greeting(name, age, gender):
    print(name, age, gender)

greeting(gender = 'male', age = 42, name = 'tony')
# tony 42 male
```

== 混合形式传参


在形参中，可以使用 `*` 当做分界线，`*` 之后的参数必须使用关键字传参的形式。

```py
# 函数只有一个位置参数，剩下的两个是关键字参数
def say(name, *, age, gender):
    print(f'name: {name}, age:{age}, gender: {gender}')

say('Tony Stark', age=42, gender='Male')
```

== 序列传参

参数列表中的 `*` 可以让序列解构。

```py
def sum(a, b, c):
    return a + b + c

a_list = [2, 3, 4]
sum(*a_list) # 9
```

== 字典传参

使用双星 `**` 对字典类型解构。

```py
def say(name, age, gender):
    print(name, age, gender)

a_dict = {
    'name': 'Tony Stark',
    'age': 42,
    'gender': 'M'
}

say(**a_dict)
# Tony Stark 42 M
```

== lambda 函数

lambda 函数是一种匿名函数。它的语法如下：

```py
lambda arguments: expression
```

比如：

```py
x = lambda a: a + 10
x(5) # 15

# lambda 函数可以有任意数量的参数
mul = lambda a, b: a * b
mul(3, 4) # 12

# 可以没有入参
three = lambda: 3
three() # 3
```

== 高阶函数

高阶函数是处理函数的函数，它的入参或返回值也是函数类型。常见的高阶函数有 `filter`、`map` 和 `reduce` 等。

`filter` 函数像一个过滤器，从一个集合中筛选符合条件的元素。它的返回值是一个过滤器实例。

```py
result = filter(lambda x: x > 4, [2, 3, 4, 5])
result # <filter object at 0x10d7b81c0>
list(result) # [5]
```

`map` 函数把序列元素映射为另一种元素。

```py
result = map(lambda n: n * 2, [1, 2, 3])
result # <map object at 0x10d760910>
list(result) # [2, 4, 6]
```

`reduce` 函数把序列中的元素压缩为一个最终值。`reduce` 函数在 `functools` 模块中，先引入，再使用。

```py
from functools import reduce

reduce(lambda a, b: a + b, [1, 2, 3, 4])
# 10
```

#pagebreak()

= 类

== 创建类

使用 `class` 创建类：

```py
# 声明一个类
class Animal():
    pass

# 创建类的一个实例
pet = Animal()
print(pet)
# <__main__.Animal object at 0x106496690>
```

使用 `__init__` 定义构造函数，可以在实例初始化时传入参数。构造函数的第一个参数是 `self`，表示即将被创建的实例。

```py
class Animal():
    def __init__(self, name): # 构造函数
        self.name = name

# 创建类的一个实例
pet = Animal('dog')
print(pet.name) # dog
```

如果要判断某个实例是否属于某个类，使用 `isinstance()` 方法。

```py
class Cat: pass

class Tiger: pass

w = Cat()
isinstance(w, Cat) # True
isinstance(w, Tiger) # True
```

== 继承

在类名后的圆括号中填入基类名称，就发生了继承关系。

```py
# 声明一个父类
class Animal():
    def __init__(self, name):
        self.name = name

    def speak(self):
        print('hi!')

# 声明一个子类，它继承了父类所有功能
class Dog(Animal):
    pass

# 创建类的一个实例
pet = Dog('旺财')
print(pet.name) # 旺财
pet.speak() # hi!
```

使用 `issubclass()` 判断两个类的父子关系：

```py
issubclass(Dog, Animal) # True
```

=== 覆盖

在子类声明的方法，可以覆盖父类的同名方法。

```py
# 声明一个类
class Animal():
    def __init__(self, name):
        self.name = name

    def speak(self):
        print('hi!')

class Dog(Animal):
    def speak(self): # 覆盖父类的同名方法
        print('汪！汪！汪！')

# 创建类的一个实例
pet = Dog('旺财')
print(pet.name) # 旺财
pet.speak() # 汪！汪！汪！
```

如果想访问父类的同名方法，可以使用 `super()` 函数。

```py
# 声明一个类
class Animal():
    def __init__(self, name):
        self.name = name

    def speak(self):
        print('hi!')

class Dog(Animal):
    def speak(self):
        super().speak() # 访问父类的同名函数
        print('汪！汪！汪！')

# 创建类的一个实例
pet = Dog('旺财')
print(pet.name) # 旺财
pet.speak()
# hi!
# 汪！汪！汪！
```

== 定义属性

使用 `property` 为类定义属性。属性在外面看是一个普通字段，内部其实由函数构成。

```py
class Animal():
    def __init__(self, name):
        self.hidden_name = name

    def set_name(self, name):
        self.hidden_name = name

    def get_name(self):
        return self.hidden_name

    # 定义一个属性
    name = property(get_name, set_name)

dog = Animal('旺财')
print(dog.name) # 旺财
dog.name = '哮天犬'
print(dog.name) # 哮天犬
```

如果只定义了 get 方法，那么这个属性是只读属性。若为其赋值，则报错。

```py
class Circle():
    def __init__(self, radius):
        self.radius = radius

    def get_diameter(self):
        return 2 * self.radius

    diameter = property(get_diameter)

c = Circle(2)
print(c.diameter) # 4

c.diameter = 8
# AttributeError: property 'diameter' of 'Circle' object has no setter
```

=== 修饰符

使用修饰符可以简化属性的创建。其中，使用 `@property` 标识 get 方法，`@[name].setter` 指示 set 方法。其中的 `[name]` 是占位符，要替换为 `property` 的实际名称。

```py
class Circle():
    def __init__(self, radius):
        self.radius = radius

    @property
    def diameter(self):
        return 2 * self.radius

    @diameter.setter
    def diameter(self, value):
        self.radius = value / 2

c = Circle(2)
print(c.radius, c.diameter) # 2 4

c.diameter = 8
print(c.radius, c.diameter) # 4.0 8.0
```

#pagebreak()

= 模块和包

在 Python 中，模块就是 `.py` 文件。

一个脚本文件中，代码行多了不方便阅读，可以拆解成不同的模块。

使用 `python -m <module-name>` 执行特定模块对应的 `.py` 脚本。比如，在本地启动一个简易服务器：

```sh
# 以当前目录为根，启动一个服务器
$ python -m http.server # 端口号默认是 8000

# 在端口号 9527 运行服务器
$ python -m http.server 9527
```

== 模块的分类

模块按照来源不同，可以分为以下 3 类：

+ Python 标准模块，Python 开箱自带
+ 第三方模块/库，来自 https://pypi.org/ 社区
+ 自定义模块

== 导入模块

使用 `import` 语句导入模块。举个例子，如果需要随机相关的操作，可以导入 `random` 模块。

```py
import random

# 生成 1～6 之间的整数，包含 1 和 6
random.randint(1, 6)

a_list = [1, 2, 3, 4]
random.shuffle(a_list) # 随机打乱列表顺序
a_list # [4, 3, 1, 2]
```

如果需要进行数学运算，导入 `math` 模块。

```py
import math

# 计算浮点数的上限整数
math.ceil(3.14) # 4
# 计算浮点数的下限整数
math.floor(3.14) # 3
```

如果想让 Python 暂停执行一段时间，可以使用 `time` 模块的 `sleep` 方法。

```py
import time

for i in [9, 5, 2, 7]:
    print(i)
    time.sleep(1) # 暂停 1 秒
```

如果模块名称较长，可以使用 `as` 关键字为其提供别名。

```py
import random as r

r.randint(1, 6)
```

使用 `from <module> import <member>` 语法，可以直接导入模块内的成员，使用时更简单。

```py
from random import randint

randint(1, 6)
```

=== 例子：下载网络资源

使用 `urllib.request` 模块可以下载网络资源。

```py
import urllib.request as req

# 远程资源路由
url = 'https://jsonplaceholder.typicode.com/todos/1'
# 本地文件地址
local_file = 'todo.json'

# 获取远程资源并下载到本地
req.urlretrieve(url, local_file)
```

== 自定义模块

每个 `.py` 文件都是一个模块。其中定义的变量和函数都会被输出。

比如，编写文件 `utils.py`：

```py
"""
This is a util module, with lots of math functions
"""

PI = 3.14

def add(a, b):
    """
    add two numbers
    """
    return a + b

def mul(a, b):
    """
    multiply two numbers
    """
    return a * b
```

在 Python REPL 环境导入该模块，使用 `dir()` 函数查看它的属性：

```py
>>> import utils
>>> dir(utils)
['PI', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'add', 'mul']
```

可以看到模块内部定义的数据和函数。

=== 模块的常见属性

模块的属性通常以双下划线开头和结尾。

- `__name__` 模块的名称。当模块是入口文件时，模块名称固定为 `__main__`，否则是模块的文件名。
- `__file__` 模块的绝对路径。
- `__doc__` 模块的文档字符串。可以使用 `help()` 函数查看。

如果你为模块编写了单元测试用例，希望模块为主文件才执行，被其他模块导入时不执行，可以将单测代码放在如下的 `if` 语句内：

```py
if __name__ == '__main__':
    # 这里放置单测用例代码
```

== 安装第三方模块

使用 `pip` 或 `pip3` 命令安装第三方模块。

```sh
$ pip install numpy
```

这个命令从 Python 官方模块批发市场 PyPI（https://pypi.org/）中下载模块到本地，方便在程序中使用。

=== 设置国内镜像

官方 PyPI 的服务器在国外，速度可能不够快，使用国内镜像可以加快模块的下载速度。

以清华大学 PyPI 镜像 @tsinghua-pypi 为例，执行如下命令设置镜像站默认值：

```sh
$ pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
```

该命令会在操作系统中创建全局配置文件。在 macOS 中，该配置文件的地址是 `~/.config/pip/pip.conf`。其内容如下：

```
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
```

要检查镜像站是否设置成功，执行如下命令：

```sh
$ pip config list
global.index-url='https://pypi.tuna.tsinghua.edu.cn/simple'
```

== 虚拟环境

Python 的虚拟环境（Virtual Environment）是一个独立的目录树，其中包含 Python 解释器和第三方库。虚拟环境用于隔离不同的 Python 项目。

使用 `venv` 模块管理虚拟环境。

```sh
# 创建新的虚拟环境
$ python -m venv myenv

# 激活虚拟环境
$ source myenv/bin/activate

# 在虚拟环境中安装第三方依赖
(myenv) $ pip install numpy
```

以上代码新建的虚拟环境，它的配置文件位于 `myenv/pyvenv.cfg` 中。

== 包

包（package）是多个模块的集合，通常是一个包含 `__init__.py` 文件的目录。

`__init__.py` 文件可以为空，它的存在就意味着当前目录是一个 Python 包。

当包被导入时，实际被导入的模块是 `__init__.py` 文件内容。

== 常用标准模块

=== os 模块

使用 os 模块，可以查看环境变量、创建目录等操作系统相关的操作。

`environ` 属性包含所有的环境变量：

```py
import os

os.environ
# environ({'LANG': 'zh_CN.UTF-8', 'SHELL': '/bin/zsh', ...})

# 获取某个环境变量的值
os.environ['LANG'] # 'zh_CN.UTF-8'
```

`system(command)` 在子 Shell 中执行操作系统命令。

```py
# 启动 Zed 编辑器
os.system('zed')

# 查看当前目录的文件列表详情
os.system('ls -l')
```

`sep` 表示路径片段分隔符。`pathsep` 表示不同路径间的分隔符。`linesep` 表示行分隔符。

```py
# macOS 系统的分隔符
os.sep # '/'
os.pathsep # ':'
os.linesep # '\n'
```

`getcwd()` 获取当前工作目录。`chdir(path)` 可以跳转到新的目录。

```py
os.getcwd() # '/current/working/directory'

# 跳转到 new-dir/ 目录
os.chdir('new-dir')
```

`mkdir(path)` 创建新路径，`rmdir(path)` 移除路径。`listdir(path)` 展示目录下的文件列表。

```py
# 创建目录
os.mkdir('hello')
os.mkdir('hello') # FileExistsError

# 删除目录
os.rmdir('hello')
```

当创建嵌套层级较深的目录时，`mkdir` 会报错 `FileNotFoundError`。使用 `makedirs()` 可以自动创建中间路径。

```py
os.mkdir('a/b/c') # FileNotFoundError

os.makedirs('a/b/c') # create successfully
```

`rename(src, dst)` 修改文件或目录的名称。

```py
# 把 foobar 更名为 foobaz 
os.rename('foobar', 'foobaz')
```

`urandom(n)` 返回 n 个字节的强加密随机数据。

```py
os.urandom(4) # b'w\xf9=j'
```

=== os.path 模块

`os.path` 模块包含众多处理文件路径的函数。

- `isdir(s)` 判断给定字符串是否现存的目录
- `isfile(s)` 判断给定字符串是否现存的文件
- `exists(path)` 判断某个路径是否存在
- `isjunction(s)` 判断给定字符串是否符号链接。junction 是 Windows 下的符号链接
- `islink(path)` 判断给定路径是否软链接
- `isabs(s)` 判断给定字符串是否绝对路径

`dirname(p)` 可以获取路径中的目录部分。

```py
os.path.dirname('a/b/c') # 'a/b'
os.path.dirname('a/b/c/') # 'a/b/c'
os.path.dirname('a.pdf') # ''
```

`basename(p)` 获取路径中的文件名部分。

```py
os.path.basename('a/b/c') # 'c'
os.path.basename('a/b/c/') # ''
os.path.basename('a.pdf') # 'a.pdf'
```

`split(p)` 方法可以拆解一个路径为元组，其结构如 `(head, tail)`。其中的 `head` 相当于 `dirname()` 返回值，`tail` 相当于 `basename()` 返回值。

```py
os.path.split('a/b/c') # ('a/b', 'c')
os.path.split('a/b/c/') # ('a/b/c', '')
os.path.split('a.pdf') # ('', 'a.pdf')
```

`splitext(p)` 把路径拆分为路径和扩展名。

```py
os.path.splitext('a/b/c.pdf') # ('a/b/c', '.pdf')
os.path.splitext('a/b/.zshrc') # ('a/b/.zshrc', '')
os.path.splitext('a/b/c.d.pdf') # ('a/b/c.d', '.pdf')
```

`join(a, *p)` 把多个路径拼接为一个路径。

```py
os.path.join('a', 'b', 'c') # 'a/b/c'
```

=== datetime 模块

`datetime` 模块用于处理时间相关的运算。它包括以下几个主要对象：

- `datetime` 日期和时间
- `date` 日期
- `time` 时间
- `timedelta` 时间差
- `timezone` 时区

使用 `datetime.now()` 查看当前时间。

```py
from datetime import datetime

datetime.now()
# datetime.datetime(2024, 8, 30, 11, 12, 30, 220751)
```

访问 `datetime` 实例的 `date()` 方法，获取当前的日期变量。通过 `time()` 方法，访问时间变量。

```py
now = datetime.now()

# 获取日期变量
today = now.date()
# datetime.date(2024, 8, 30)

# 获取时间变量
time = now.time()
# datetime.time(11, 15, 38, 554175)
```

要获取 `datetime` 实例的年月日时分秒等信息，访问如下字段：

- `year` 年份
- `month` 月份
- `day` 日期
- `hour` 时
- `minute` 分
- `second` 秒
- `microsecond` 微秒，一百万分之一秒
- `weekday()` 周几，`0` 表示周一，...，`6` 表示周日

在 `f-string` 中可以使用占位符格式化 datetime 实例的输出。

```py
import datetime

today = datetime.date(2024, 8, 30)
f'today is {today:%A, %B %d, %Y}'
# 'today is Friday, August 30, 2024'
```

这些占位符的含义如下所示：

```
年    %Y 四位数字    %y 两位数字
月    %m 两位数字    %B 英文全称     %b 英文简称
日    %d 两位数字
时    %H 24小时制    %I 12小时制    %p am/pm
分    %M 两位数字
秒    %S 两位数字
周    %w 一位数字     %A 英文全称     %a 英文简称
微秒  %f 两位数字
一年中的第几天 %j 三位数字
一年中的第几周 %U 两位数字（周日为一周开始）
一年中的第几周 %W 两位数字（周一为一周开始） 
```

使用 `datetime.strptime(str, format)` 把字符串转换为 `datetime` 实例。

```py
from datetime import datetime

time_str = '2024-08-30 13:15:02'
d = datetime.strptime(time_str, '%Y-%m-%d %H:%M:%S')
d # datetime.datetime(2024, 8, 30, 13, 15, 2)
```

使用 `datetime.strftime(format)` 把 `datetime` 实例转换为字符串。

```py
n = datetime.now()
n.strftime('%Y/%m/%dT%H:%M:%S)
# '2024/08/30T13:20:54'
```

通过 `timedelta` 实例，可以获取一定时间后的日期。

```py
from datetime import datetime, timedelta

n = datetime.now()
# datetime.datetime(2024, 8, 30, 13, 24, 57, 61609)

n + timedelta(days=1, hours=2)
# datetime.datetime(2024, 8, 31, 15, 24, 57, 61609)
```

两个日期相减，可以得到 `timedelta` 实例。

```py
from datetime import datetime

start = datetime(2023, 12, 31)
end = datetime(2024, 8, 30)

diff = end - start
diff.days # 243
```

=== time 模块。

`time` 模块也提供了和时间相关的一些功能。

`time()` 函数返回 Unix 时间戳，以秒为单位。如果系统时钟提供更精确的信息，它也能返回浮点数。

```py
import time

time.time() # 1724989306.74621
```

`time` 模块另一个让人津津乐道的功能是 `sleep(seconds)` 函数，它可以让进程暂停几秒钟。

```py
time.sleep(3) # 暂停 3 秒后再执行
```

#pagebreak()

= 数学运算

== math 模块

Python 自带的 `math` 模块支持常见的数学运算。

```py
import math

math.pi # 3.141592653589793
math.e # 2.718281828459045

# 绝对值
math.fabs(-271.1) # 271.1

math.floor(3.14) # 3
math.ceil(3.14) # 4

# 阶乘
math.factorial(6) # 720

# 对数，默认以 e 为底
math.log(1) # 0.0
math.log(math.e) # 1.0
math.log(8, 2) # 3

# 指数
math.pow(2, 3) # 8.0

# 平方根
math.sqrt(4) # 2.0

# 三角函数
math.sin(math.pi / 6) # 0.49999999999999994
math.cos(math.pi / 6) # 0.8660254037844387
math.tan(math.pi / 4) # 0.9999999999999999

# 斜边长
math.hypot(3, 4) # 5.0

# 角度 -> 弧度
math.radians(180) # 3.141592653589793
# 弧度 -> 角度
math.degrees(math.pi) # 180.0
```

== 复数运算

复数支持基本的四则运算：

```py
1j * 1j # (-1 + 0j)
```

复数运算使用 `cmath` 模块。

== 小数精确计算

`decimal` 模块提供了小数精确计算的功能。

```py
from decimal import Decimal
a = Decimal('0.1')
b = Decimal('0.2')
c = a + b # Decimal('0.3')
```

保留小数点后有效数字个数，使用 `quantize()` 方法。

```py
total = Decimal('21.1894')
penny = Decimal('0.01)
total.quantize(penny) # Decimal('21.19')
```

== 有理数运算

有理数运算使用 `fractions` 模块。

```py
from fractions import Fraction
Fraction(1, 3) * Fraction(2, 3) # Fraction(2, 9)
```

== NumPy

NumPy @numpy 是一个知名数学计算库，它的核心数据结构是多维数组 `ndarray`。秩（rank）表示数组的维度。

```py
import numpy as np

# 创建数组
b = np.array([2, 4, 6, 8])
print(b.ndim) # 1 数组的秩
print(b.size) # 4 元素个数
print(b.shape) # (4,) 每一维度的元素数量
```

#pagebreak()

= 数据可视化

== matplotlib

使用 pip 安装 matplotlib:

```sh
$ pip install matplotlib
```

绘制一个简单的正弦波：

```sh
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 200)
y = np.sin(x)

fig, ax = plt.subplots()
ax.plot(x, y)
plt.show()
```

#pagebreak()

= 操作文件和目录

== 读取文件

使用 `open(file, mode)` 方法打开文件，`file` 是文件路径，`mode` 表示打开模式，模式的可选值有：

- `'r'` 只读模式（默认值）
- `'w'` 覆盖模式
- `'x'` 独占模式，如果文件已存在，则报错
- `'a'` 追加模式
- `'b'` 二进制模式
- `'t'` 文本模式（默认值）
- `'+'` 打开文件用于更新（读写兼备）

```py
f = open('data.txt', 'r') # 以只读模式打开文件
f # <_io.TextIOWrapper name='data.txt' mode='r' ...>

content = f.read() # 读取文件全部内容
f.close() # 关闭文件实例
```

当文件内容很大时，可以向 `read()` 函数传入参数，只读取部分内容。

```py
# 只读取前 8 个字符
content = f.read(8)

# 继续再次读取 8 个字符
content = f.read(8)
```

使用 `readline()` 每次只读取一行。

```py
# 读取第一行
f.readline() # '第一行\n'

# 读取第二行
f.readline() # '第二行\n'
```

`readlines()` 读取所有行，并返回一个列表：

```py
f.readlines()
# ['第一行\n', '第二行\n', '第三行\n', '第四行']
```

=== with 语句

如果忘记调用 `f.close()` 释放文件，可能会造成内存泄露。使用 `with` 语句可以自动关闭文件。

```py
with open('data.txt', 'r') as f:
    content = f.read()
    print(content)
```

== 写入文件

使用 `w` 模式打开文件后，可以写入数据，此时旧数据会被覆盖。

使用 `write()` 方法，向文件写入内容。

```py
with open('data.txt', 'w') as f: # 以写入模式打开文件
    f.write('hello\nfoobar') # 写入内容
```

使用 `a` 模式打开文件，新的内容会追加的原有内容之后。

使用 `writelines(list)` 函数向文件写入多行文本。它的入参是序列类型，每个元素结尾需要有换行符：

```py
data = ['第一行\n', '第二行\n', '第三行']
with open('lines.txt', 'w') as f:
    f.writelines(data)
```

== 删除文件

使用 `os.remove(path)` 删除文件。删除前，建议使用 `os.path.exists(path)` 判断它是否存在，如果存在再删除。

```py
import os

# 待删除文件名
file_to_del = 'data.txt'

# 检测文件是否存在
if os.path.exists(file_to_del):
    os.remove(file_to_del) # 删除文件
    print(f'已删除 {file_to_del}')
else:
    print(f'{file_to_del} 不存在')
```

== 创建目录

创建一层目录使用 `os` 模块提供的 `mkdir` 方法。为了防止重复创建目录，建议先检测目标目录是否存在。

```py
import os # 导入必要的模块

target_dir = 'output'
if not os.path.isdir(target_dir): # 当目录不存在时
    os.mkdir(target_dir) # 创建目录
else:
    print("目录已经存在")
```

创建多层嵌套目录时，使用 `os.makedirs()` 方法。

#pagebreak()

= 网络请求

== 内置模块 urllib

```py
import json
from urllib.request import urlopen

url = 'https://jsonplaceholder.typicode.com/todos/'
response = urlopen(url) # 打开网址
contents = response.read() # 读取字节流
text = contents.decode('utf8') # 解码为纯文本
data = json.loads(text) # 将 JSON 字符串解析为列表

for item in data[0:6]:
    print(f"{item['title']} - {item['completed']}")
```

== requests 模块

社区提供的 `requests` 模块 @requests 可以简化网络请求的写法。

```py
import requests

url = 'https://jsonplaceholder.typicode.com/todos/'
response = requests.get(url)
data = response.json()

for item in data[0:6]:
    print(f"{item['title']} - {item['completed']}")
```

TODO

#pagebreak()

= 加密解密

`hashlib` 库包含大量哈希函数，通过 `algorithms_available` 属性，可以查看当前解释器支持的所有算法。

```py
import hashlib

hashlib.algorithms_available
# {'sha384', 'sha512', 'sha3_256', 'sha256', 'shake_128', 'sha512_224', 'sha3_384', 'md5', 'sha3_512', 'sm3', 'sha224', 'shake_256', 'blake2b', 'sha1', 'sha3_224', 'blake2s', 'md5-sha1', 'sha512_256', 'ripemd160'}
```

== MD5

MD5 函数的输出值是 128 比特的哈希值，常用于检查数据完整性。它的计算速度很快，但有安全问题，比如容易产生哈希碰撞。

相关函数有：

- `str.encode()` 把字符串转换为哈希函数可以使用的字节
- `md5_hash = hashlib.md5()` 创建 MD5 哈希实例
- `md5_hash.update()` 向 MD5 哈希函数添加字符串
- `md5_hash.digest()` 返回字节格式的编码数据
- `md5_hash.hexdigest()` 返回十六进制的编码数据

下面代码计算字符串 `'abc'` 的 MD5 值。

```py
import hashlib

# 通过 md5 构造函数传入字符串
result = hashlib.md5(b'abc')

result.digest()
# b'\x90\x01P\x98<\xd2O\xb0\xd6\x96?}(\xe1\x7fr'

result.hexdigest()
# '900150983cd24fb0d6963f7d28e17f72'
```

上述代码也可以简写为一行代码：

```py
hashlib.md5('abc'.encode()).hexdigest()
# '900150983cd24fb0d6963f7d28e17f72'
```

除此之外，也可以通过 `update()` 方法传入字符串。

```py
import hashlib

md5 = hashlib.md5()
md5.update(b'abc') # 通过 update() 方法传入字符串
md5.hexdigest()
# '900150983cd24fb0d6963f7d28e17f72'
```

如果要为文件生成 MD5 哈希值，使用如下代码：

```py
import sys
import hashlib

# 获取文件路径
file_path = sys.argv[1]

md5 = hashlib.md5()

with open(file_path, 'rb') as f:
    for chunk in iter(lambda: f.read(4096), b''):
        md5.update(chunk)

# 输出文件哈希
md5.hexdigest()
```

#pagebreak()

= 游戏开发

pygame @pygame 是一个开源的游戏框架。

安装命令：

```bash
python3 -m pip install -U pygame==2.6.0

# or
pip3 install pygame==2.6.0
```

验证是否安装成功。

```python
>>> import pygame
pygame 2.6.0 (SDL 2.28.4, Python 3.12.5)
Hello from the pygame community. https://www.pygame.org/contribute.html
```

#pagebreak()

= 网络服务器

== Django

TODO

== Flask

TODO

== FastAPI

TODO

#pagebreak()

= 桌面应用

== PyQt

TODO

#pagebreak()

#bibliography("bib.yml", title: "参考文献")